using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

using Yorrick.FlickrMetadataSynchr.Data;

namespace Yorrick.FlickrMetadataSynchr.Match
{
    public delegate T GetLocalMatchKey<T>(LocalPictureMetadata localPictureMetadata);

    public delegate T GetFlickrMatchKey<T>(FlickrPictureMetadata flickrPictureMetadata);

    public delegate bool IsNullMatchKey<T>(T t);

    /// <summary>
    /// Class that matches pictures based on a single metadata key.
    /// </summary>
    /// <typeparam name="T">The type of the metadata key to match on.</typeparam>
    internal class MetadataSingleValueMatcher<T> : MetadataMatcherBase
    {
        #region Private fields
        /// <summary>
        /// Metadata for the local set.
        /// </summary>
        private Dictionary<T, LocalPictureMetadata> _localSet;

        /// <summary>
        /// Metadata for the Flickr set.
        /// </summary>
        private Dictionary<T, FlickrPictureMetadata> _flickrSet;

        /// <summary>
        /// Delegate to a function that returns the match key for a local picture.
        /// </summary>
        private GetLocalMatchKey<T> _getLocalMatchKey;

        /// <summary>
        /// Delegate to a function that returns the match key for a Flickr picture.
        /// </summary>
        private GetFlickrMatchKey<T> _getFlickrMatchKey;

        /// <summary>
        /// Delegate to determine if a match key value should not used.
        /// </summary>
        private IsNullMatchKey<T> _isNullMatchKey;

        /// <summary>
        /// The display name for the metadata property on which value is matched.
        /// </summary>
        private string _propertyDisplayName;
        #endregion

        #region Constructor
        /// <summary>
        /// Constructs an instance.
        /// </summary>
        /// <param name="propertyDisplayName">The display name for the metadata property on which value is matched.</param>
        /// <param name="getLocalMatchKey">Delegate to a function that returns the match key for a local picture.</param>
        /// <param name="getFlickrMatchKey">Delegate to a function that returns the match key for a Flickr picture.</param>
        /// <param name="isNullMatchKey">Delegate to determine if a match key value should not used.</param>
        public MetadataSingleValueMatcher(string propertyDisplayName, GetLocalMatchKey<T> getLocalMatchKey, GetFlickrMatchKey<T> getFlickrMatchKey, IsNullMatchKey<T> isNullMatchKey)
        {
            _propertyDisplayName = propertyDisplayName;
            _getLocalMatchKey = getLocalMatchKey;
            _getFlickrMatchKey = getFlickrMatchKey;
            _isNullMatchKey = isNullMatchKey;
        }
        #endregion

        #region Protected methods
        /// <summary>
        /// Performs the matching of Flickr pictures with local pictures.
        /// </summary>
        /// <remarks>This method should only be called once.</remarks>
        protected override void DoMatch()
        {
            _localSet = new Dictionary<T, LocalPictureMetadata>(LocalPicturesToMatch.Count, EqualityComparer<T>.Default);
            _flickrSet = new Dictionary<T, FlickrPictureMetadata>(FlickrPicturesToMatch.Count, EqualityComparer<T>.Default);

            BuildUpLocalSet();

            BuildUpFlickrSet();

            MatchFlickrPictures();

            DetermineUnmatchedLocalPictures();
        }

        /// <summary>
        /// Determine which local pictures remain unmatched and add them to the unmatched list.
        /// </summary>
        private void DetermineUnmatchedLocalPictures()
        {
            foreach (T metadataKey in _localSet.Keys)
            {
                LocalPictureMetadata localMetadata = _localSet[metadataKey];

                if (!_flickrSet.ContainsKey(metadataKey))
                {
                    Trace.TraceInformation("Unmatched local picture based on {0} with filename '{1}'.", _propertyDisplayName, localMetadata.FilePath);
                    UnmatchedLocalPictures.Add(localMetadata);
                }
            }
        }

        /// <summary>
        /// Tries to match each Flickr picture in the set of potentially matchable pictures. Matched and unmatched pictures
        /// are added to separate lists.
        /// </summary>
        private void MatchFlickrPictures()
        {
            foreach (T metadataKey in _flickrSet.Keys)
            {
                FlickrPictureMetadata flickrMetadata = _flickrSet[metadataKey];

                if (_localSet.ContainsKey(metadataKey))
                {
                    LocalPictureMetadata localMetadata = _localSet[metadataKey];

                    MatchedPictures.Add(new PicturePair(localMetadata, flickrMetadata, SyncDirection.None));

                    Trace.TraceInformation("Matched Flickr picture based on {0} with id {1} with local picture with filename '{2}'.", _propertyDisplayName, flickrMetadata.FlickrId, localMetadata.FilePath);
                }
                else
                {
                    Trace.TraceInformation("Unmatched Flickr picture based on {0} with id {1}.", _propertyDisplayName, flickrMetadata.FlickrId);
                    UnmatchedFlickrPictures.Add(flickrMetadata);
                }
            }
        }

        /// <summary>
        /// Builds up the set of Flickr pictures that can potentially be matched.
        /// </summary>
        /// <remarks>Flickr pictures with no match key or duplicate keys cannot be matched and are added to
        /// the list of unmatched pictures.</remarks>
        private void BuildUpFlickrSet()
        {
            foreach (FlickrPictureMetadata metadata in FlickrPicturesToMatch)
            {
                T matchKey = _getFlickrMatchKey(metadata);

                if (_isNullMatchKey(matchKey))
                {
                    Trace.TraceWarning("Unmatched Flickr picture based on {0} with {1} (null key).", _propertyDisplayName, metadata.FlickrId);
                    UnmatchedFlickrPictures.Add(metadata);
                }
                else if (_flickrSet.ContainsKey(matchKey))
                {
                    Trace.TraceWarning("Flickr set already contains picture with match key {0}.", matchKey);
                    UnmatchedFlickrPictures.Add(metadata);
                }
                else
                {
                    _flickrSet.Add(matchKey, metadata);
                }
            }
        }

        /// <summary>
        /// Builds up the set of local pictures that could potentially be matched.
        /// </summary>
        /// <remarks>Local pictures with no match key or duplicate keys cannot be matched and are added to
        /// the list of unmatched pictures.</remarks>
        private void BuildUpLocalSet()
        {
            foreach (LocalPictureMetadata metadata in LocalPicturesToMatch)
            {
                T matchKey = _getLocalMatchKey(metadata);

                if (_isNullMatchKey(matchKey))
                {
                    Trace.TraceWarning("Unmatched local picture based on {0} with {1} (null key).", _propertyDisplayName, metadata.FilePath);
                    UnmatchedLocalPictures.Add(metadata);
                }
                else if (_localSet.ContainsKey(matchKey))
                {
                    Trace.TraceWarning("Local set already contains picture with match key {0}.", matchKey);
                    UnmatchedLocalPictures.Add(metadata);
                }
                else
                {
                    _localSet.Add(matchKey, metadata);
                }
            }
        }
        #endregion
    }
}
